(function (fruit, util) {

    var DynamicObject = fruit.define(fruit.ObservableObject, {
        methods: {
            init: function (data) {
                this._data = data || {};
                this._cachedValues = {};
            },
            get: function (inName, orig) {
                var value = this.inherited(inName);
                if (orig) {
                    return value;
                }
                else {
                    var cachedValue = this._cachedValues[inName];
                    if (cachedValue === undefined) {
                        return this._cachedValues[inName] = wrap(value);
                    }
                    else {
                        return cachedValue;
                    }
                }
            },
            set: function (inName, inValue) {
                var oldValue = this.get(inName, true);
                if (oldValue !== inValue) {
                    if (this[inName]) {
                        this[inName](inValue);
                    }
                    else if (this._data) {
                        this._data[inName] = inValue;
                    }

                    this._cachedValues[inName] = undefined;
                    this.notifyPropertyChanged(inName, inValue, oldValue);
                }
            },
            toJSON: function () {
                return this._data;
            }
        }
    });

    var DynamicCollection = fruit.define(fruit.ObservableCollection, {
        methods: {
            init: function (data) {
                this._data = data || [];
                this._cachedItems = {};
            },
            getItem: function (index, orig) {
                var item = this.inherited(index);
                if (orig) {
                    return item;
                }
                else {
                    var cachedValue = this._cachedItems[index];
                    if (cachedValue === undefined) {
                        return this._cachedItems[index] = wrap(item);
                    }
                    else {
                        return cachedValue;
                    }
                }
            },
            forEach: function (callback, context) {
                util.forEach(this._data, function (item, index) {
                    var wrappedItem;
                    var cachedValue = this._cachedItems[index];
                    if (cachedValue === undefined) {
                        wrappedItem = this._cachedItems[index] = wrap(item);
                    }
                    else {
                        wrappedItem = cachedValue;
                    }
                    callback.call(context, wrappedItem, index);
                }, this);
            },
            notifyCollectionChanged: function (data) {
                if (data && data.item) {
                    data.item = wrap(data);
                }
                this.fire('collectionChanged', data);
            },
            toJSON: function () {
                return this._data;
            }
        }
    });


    function wrap(data) {
        if (util.isArray(data)) {
            return new DynamicCollection(data);
        }
        else if (util.isObject(data)) {
            return new DynamicObject(data);
        }
        else {
            return data;
        }
    }

    fruit.wrapModel = wrap;
})(fruit, fruit.util);